package com.gamebase.net.rpc
{
	import flash.events.Event;
	import flash.events.IEventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.Socket;
	import flash.system.Security;
	import flash.utils.ByteArray;
	import flash.utils.Dictionary;
	import flash.utils.Endian;
	import flash.utils.Timer;
	import flash.utils.setTimeout;
	
	
	public class Session implements IEventDispatcher
	{
		
		private var socket:Socket;
		private var _iProtocolMap:IProtocolMap;
		private var expireTimer:Timer;
		private var hbInterval:uint;
		private var hbTimer:Timer;
		private var hbBuffer:ByteArray;
		private var recvHeader:SocketHeader;
		private var sendHeader:SocketHeader;
		private var requests:RequestCollection;
		private var events:Dictionary;
		private var listeners:Dictionary;
		private var debugList:Array;
		
		private var _ip:String;
		private var _port:int;
		private var _contect:Function;
		private var _onClose:Function;
		private var _tryConnectNum:int;
		
		
		
		public var maxMsgSize:uint;
		public var expireInterval:uint;
		
		public static const E_SYSTEMERROR:String = "SESSION_SYSTEMERROR";
		
		private static var _instance:Session;
		public static function get instance():Session
		{
			if(_instance==null)
				_instance = new Session();
			return _instance;
		}
		
		public function Session()
		{
			hbInterval = 0
			
			recvHeader = new SocketHeader();
			sendHeader = new SocketHeader();
			
			requests = new RequestCollection();
			events = new Dictionary();
			listeners = new Dictionary();			

			debugList = new Array();
		}
		
		
		
		public function connect(
			ip:String,port:int,
			iProtocolMap:IProtocolMap,
			$contect:Function=null, $colseHandler:Function=null, 
			maxMsgSize:uint=0, expireInterval:uint=0, tryConnectNum:int=500
		):void
		{	
			_ip = ip;
			_port = port;
			_contect = $contect;
			_onClose = $colseHandler;
			_iProtocolMap = iProtocolMap;
			this.maxMsgSize = maxMsgSize;
			this.expireInterval = expireInterval;
			_tryConnectNum = tryConnectNum;
			
//			Security.loadPolicyFile("http://"+host+"/crossdomain.xml");			
			if(socket == null)
			{
				socket = new Socket();
				socket.endian = Endian.BIG_ENDIAN;//Endian.LITTLE_ENDIAN;
				registerEvent();
				Security.loadPolicyFile("xmlsocket://"+ip+":"+port);
				//Security.loadPolicyFile("xmlsocket://"+ip+":843"/*+"/crossdomain.xml"*/);
				Security.allowDomain( "*" );
				Security.allowInsecureDomain("*");
			}
			
			socket.connect(ip,port);
		}
		
		private function connectFail(evt:Event):void
		{
			trace(evt.toString());
			_tryConnectNum--;
			close();
			flash.utils.setTimeout(connect, 1000, _ip, _port, _iProtocolMap, _contect, _onClose, _tryConnectNum);
		}
		
		public function close():void
		{
			if(socket && socket.connected)			
				socket.close();
		}
		
		public function hearbeat(hb:uint):void
		{
			if(socket.connected)
			{
				hbInterval = hb;
				
				if(hbInterval>0&&hbTimer==null)
				{
					var ph:SocketHeader = new SocketHeader();
					hbBuffer = ph.buffer;
					ph.iid = 2;
					ph.sid = 2;
					ph.encode();
					hbBuffer.position = 0;
					ph.encodeLength();					
					hbTimer = new Timer(hbInterval*1000);
					hbTimer.addEventListener(TimerEvent.TIMER,onHBTimeout);
					hbTimer.start();
				}
			}
		}
		
		public function get connected():Boolean
		{
			return socket.connected;
		}
		
		//设置校验码，一般用来记住玩家id等身份表识信息
		public function setValidate(validateId:uint):void
		{
			sendHeader.validate = validateId;
		}
		
		public function call(method:IMethod,mid:uint,callback:Function,param:Object=null,sid:uint=1):Serializer
		{
			sendHeader.reset();			
			if(callback!=null)
			{
				var request:Request = new Request();
				request.method = method;
				request.callback = callback;
				request.param = param;
				requests.pushRequest(request);
				//trace("send_tsn:"+request.tsn);
			}			
			sendHeader.iid = method.getIID();
			sendHeader.sid = sid;			
			sendHeader.encode();			
			return sendHeader.serializer;
		}
		
		public function request(iid:int, sid:int, data:Object=null):void
		{
			sendHeader.reset();
			sendHeader.iid = iid;
			sendHeader.sid = sid;
			var buffer:ByteArray = sendHeader.serializer.getBuffer();
			
//			var i:uint = 0;
//			while(i<$data.length)
//			{
//				buffer.writeObject($data[i]);
//				i++;
//			}
			sendHeader.encode(data);
			trace("发送数据(长度:"+buffer.length+")："+buffer.toString())
			
			socket.writeBytes(buffer);
			socket.flush();
			
		}
		
		public function sendBuffer(ba:ByteArray):void
		{	
			ba.position = 0;
			sendHeader.encodeLength();			
			printf(sendHeader);
			socket.writeBytes(ba);
			socket.flush();
		}
		
		public function addEvent(event:IEvent):void
		{
			events[event.getIID().toString()] = event;
		}
		
		public function removeEvent(event:IEvent):IEvent
		{
			var event:IEvent = events[event.getIID().toString()];
			events[event.getIID().toString()]=null;
			return event;
		}
		
		private function getEvent(iid:uint):IEvent
		{
			return events[iid.toString()];
		}
		
		private function registerEvent():void
		{
			socket.addEventListener(Event.CONNECT,onConnect);		
			socket.addEventListener(IOErrorEvent.IO_ERROR, connectFail);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, connectFail);
			socket.addEventListener(ProgressEvent.SOCKET_DATA,onSocketData);
			socket.addEventListener(Event.CLOSE,onClose);
		}
		
		private function unregisterEvent():void
		{
			if(socket)
			{
				socket.removeEventListener(Event.CONNECT,onConnect);		
				socket.removeEventListener(IOErrorEvent.IO_ERROR, connectFail);
				socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, connectFail);
				socket.removeEventListener(ProgressEvent.SOCKET_DATA,onSocketData);
				socket.removeEventListener(Event.CLOSE,onClose);
			}
		}
		
		private function onConnect(e:Event):void
		{
			if(socket.connected)
			{
				if(expireInterval>0)
				{
					expireTimer = new Timer(expireInterval*1000);
					expireTimer.addEventListener(TimerEvent.TIMER,onExpireTimeout);
					expireTimer.start();
				}
			}
			if(_contect != null)
				_contect(e);
		}
		
		private function onExpireTimeout(e:TimerEvent):void
		{
			requests.clearTimeout(expireInterval*1000);
		}
		
		private function onHBTimeout(e:TimerEvent):void
		{
			socket.writeBytes(hbBuffer);
			socket.flush();
		}
		
		private function onSocketData(e:ProgressEvent):void
		{
			//trace("recv_length:"+stream.bytesAvailable);
			
			while(socket.bytesAvailable>0)
			{
				//接收消息头
				if(recvHeader.length==0)
				{
					if(socket.bytesAvailable<SocketHeader.SCheaderLength)					
						return;	
					socket.readBytes(recvHeader.buffer,0,SocketHeader.SCheaderLength);
					recvHeader.decodeHead();
					if(maxMsgSize>0 && recvHeader.length>maxMsgSize)
					{
						trace("数据包长度不合法.");
						socket.close();
					}
					if(recvHeader.err_code)//有错误码则不继续解码消息体
					{
						if(recvHeader.flag==2) // c->s
							onMethodResult();					
						else if(recvHeader.flag==3) // s->c				
							onEventResult(recvHeader.err_code);					
						else
							trace("数据流向标识不正确.");	
						recvHeader.reset();
						return;
					}
				}
				//接收消息体
				var less:int = recvHeader.length - recvHeader.buffer.length;
				if(less<0)
				{
					trace("服务端消息长度写入错误");
					less = 0;
				}
					
				if(socket.bytesAvailable>=less)
				{
					if(less >0)
						socket.readBytes(recvHeader.buffer,recvHeader.buffer.length,less);
					recvHeader.buffer.position = SocketHeader.SCheaderLength;
					printf(recvHeader);
					if(recvHeader.flag==2) // c->s
						onMethodResult();					
					else if(recvHeader.flag==3) // s->c				
						onEventResult(0);					
					else
						trace("数据流向标识不正确.");	
					recvHeader.reset();
				}
				else				
					socket.readBytes(recvHeader.buffer,recvHeader.buffer.length,socket.bytesAvailable);
				
				if(socket.connected == false)
				{
					if(_onClose != null)
						_onClose(e);
					return;
				}
			}
		}
		
		private function onSecurityError(e:SecurityErrorEvent):void
		{
			Debug.append(e.text);			
		}
		
		private function onIOError(e:IOErrorEvent):void
		{
			Debug.append(e.text);			
			socket.close();
		}
		
		private function onClose(e:Event):void
		{
			Debug.append("SOCKET CLOSED!");			
			unregisterEvent();
			events = new Dictionary();
			requests = new RequestCollection();
			
			if(expireInterval>0)
			{
				expireTimer.removeEventListener(TimerEvent.TIMER,onExpireTimeout);
				expireTimer.stop();
			}
			
			if(hbInterval>0)
			{
				hbTimer.removeEventListener(TimerEvent.TIMER,onHBTimeout);
				hbTimer.stop();
			}
			if(_onClose != null)
				_onClose(e);
		}
		
		private function onMethodResult():void
		{
			var request:Request = new Request();//tt requests.popRequest(recvHeader.tsn);
			
			if(request!=null)
			{		
				var result:MethodResult = null;				
				result = request.method.getResult(recvHeader.sid,recvHeader.serializer);				
				result.param = request.param;				
				onSystemError(request,recvHeader,result);				
				request.callback.call(null,result);
			}
			else
			{
				Debug.append("C->S CALL BACK ERROR，SERVER TYPE:",recvHeader.iid,"METHOD:",recvHeader.sid);
			}
			
		}
		
		private function onEventResult(errorCode:int):void
		{
			
			var protocalType:String = recvHeader.iid+","+recvHeader.sid;					
			var cbs:Array = listeners[protocalType] as Array;
			var cbType:* = this._iProtocolMap.getType(protocalType);
			
			var reaceiveData:Object;
			var fun:Function;
			trace("收到socket数据,类型："+protocalType+" ,  数据长度:"+recvHeader.length)
			if(errorCode > 0)//服务端返回有错误码，则给回调赋值null
				reaceiveData = null;
			else
			{
				if(cbType == null)//响应空数据
					reaceiveData = null;
				else
					reaceiveData = recvHeader.serializer.decodeData(cbType, recvHeader.buffer);
			}
				
			if(cbs!=null)
			{
				if(cbs.length <= 1)
				{
					fun = cbs[0];
					if(fun.length == 0)
						fun.call(null);
					else if(fun.length == 1)
						fun.call(null,reaceiveData);
					else if(fun.length == 2)
						fun.call(null,reaceiveData, recvHeader.err_code);
				}
				else
				{
					for each (fun in cbs)	
					{
						
						if(fun.length == 0)
							fun.call(null);
						else if(fun.length == 1)
							fun.call(null,reaceiveData);
						else if(fun.length == 2)
							fun.call(null,reaceiveData, recvHeader.err_code);
					}
					
				}
							
			} 
			else			
				Debug.append("S->C HANDLER ERR.","SERVER TYPE:",recvHeader.iid,"METHOD ID:",recvHeader.sid);			
			
		}
		
		private function onSystemError(request:Request,header:SocketHeader,result:MethodResult):void
		{
			if(result.errorCode<0x0000ffff)
			{
				var cbs:Array = listeners[E_SYSTEMERROR] as Array;				
				if(cbs==null) return;				
				for(var i:uint;i<cbs.length;i++)				
					cbs[i].call(request,header,result);				
			}
		}
		private function printf(socketHeader:SocketHeader):void
		{
			if(debugList.length==0)
				return;
			for each(var t:SocketHeader in debugList)
			{
				if(t.iid==socketHeader.iid && t.sid==socketHeader.sid && t.flag==socketHeader.flag)
				{
					Debug.append("---- ","SERVER TYPE:",socketHeader.iid,"METHOD:",
						socketHeader.sid,"FLAG:",socketHeader.flag," -----");
					var buffer:ByteArray = new ByteArray();
					buffer.writeBytes(socketHeader.buffer,0,socketHeader.buffer.length);
					var result:String = new String();
					var str:String = buffer.toString();
					for(var j:int=0;j<buffer.length;j++)
					{
						var temp:String = str.charCodeAt(j).toString(16);
						if(temp.length==1)
							temp = "0"+temp;
						result += temp +" ";                   
					} 
					Debug.append(result);
					Debug.append("------- END -------");
					continue;
				}				
			}
		}
		
		public   function addType(iid:int,method:int,flag:int =3):void
		{
			var p:SocketHeader = new SocketHeader();
			p.sid = method;
			p.iid = iid;
			p.flag = flag;
			debugList.push(p);
		}
		
		private function addListener(type:String,listener:Function):void
		{
			var cbs:Array = listeners[type] as Array;
			if(cbs==null) 
			{
				cbs = new Array();
				listeners[type] = cbs;
				cbs.push(listener);
			}
			else
			{
				if(cbs.indexOf(listener)>-1) return;
				
				cbs.push(listener);
			}
		}
		
		public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
		{
			if(type==SecurityErrorEvent.SECURITY_ERROR
				||type==IOErrorEvent.IO_ERROR
				||type==Event.CLOSE)
			{
				socket.addEventListener(type,listener,useCapture,priority,useWeakReference);
			}
			else
			{
				addListener(type,listener);
			}
		}
		
		public function dispatchEvent(event:Event):Boolean 
		{
			return true;
		}
		  
		public function hasEventListener(type :String):Boolean 
		{
			return listeners[type]!=null;
		}
		
		public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void 
		{
			if(hasEventListener(type))
			{
				var cbs:Array = listeners[type] as Array;
				var i:int = cbs.indexOf(listener);
				if(i>-1) cbs.splice(i,1); 
				if(cbs.length==0) listeners[type]=null;
			}
		}
		
		public function willTrigger(type :String):Boolean 
		{
			return hasEventListener(type);
		}
		
		public function destroy():void
		{
			if(hbTimer)
			{
				hbTimer.removeEventListener(TimerEvent.TIMER,onHBTimeout);
				hbTimer.stop();
			}
			if(expireTimer)
			{
				expireTimer.addEventListener(TimerEvent.TIMER,onExpireTimeout);
				expireTimer.stop();
			}
			requests.clearTimeout(expireInterval*1000);
			unregisterEvent();
			_iProtocolMap = null;
			close();
			
		}
	}
}